/*
 * Copyright (c) 2007 jMouse Gestures
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'jMouse Gestures' nor the names of its contributors 
 *   may be used to endorse or promote products derived from this software 
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.jmg.swing;

import java.awt.Color;


/**
 * TrailFader class fades a colour between a starting colour and a fade colour
 * over the given fade time.
 *
 * @author  Christopher Martin
 * @version $Id: TrailFader.java 13 2007-05-24 12:13:13Z GentlemanHal $
 */
public class TrailFader implements
    Cloneable
{
    /** 
     * The value to multiply a time in millisecond to convert it to a time in
     * seconds.
     */
    public static final float MILLISECONDS_TO_SECONDS = 0.001f;
    
    /**
     * The value to multiply a time in second to convert it to a time in
     * milliseconds.
     */
    public static final int SECONDS_TO_MILLISECONDS = 1000;
    
    private Color baseColour, fadeTo;
    private long fadeTime;
    private long lastUpdate  = 0L;
    private long firstUpdate = -1;
    
    
    
    
    
    /**
     * Creates a new instance of <code>TrailFader</code>.
     *
     * @param  baseColour The starting colour.
     * @param  fadeTo The colour to fade to, the alpha value of this colour is
     *         ignored as it will always fade the alpha to zero.
     * @param  fadeTimeInSeconds The amount of time to take to completely fade
     *         to the fadeTo colour.
     * @throws java.lang.IllegalArgumentException If a fade time equal to or
     *         less than zero is passed or if a <code>null</code> argument is
     *         passed for either colour.
     */
    public TrailFader(final Color baseColour, final Color fadeTo, final float fadeTimeInSeconds) 
        throws IllegalArgumentException
    {
        if (null == baseColour) {
            throw new IllegalArgumentException("baseColour was null!");
        }
        if (null == fadeTo) {
            throw new IllegalArgumentException("fadeTo was null!");
        }
        if (fadeTimeInSeconds <= 0f) {
            throw new IllegalArgumentException("fadeTimeInSeconds must be > 0: " 
                                               + fadeTimeInSeconds);
        }
        
        
        this.baseColour = baseColour;
        this.fadeTo     = new Color(fadeTo.getRed(), fadeTo.getGreen(), fadeTo.getBlue(), 0);
        fadeTime        = Math.round(fadeTimeInSeconds * SECONDS_TO_MILLISECONDS);
    }
    
    
    
    
    
    /**
     * Returns the starting colour of this fader.
     * 
     * @return The base colour.
     */
    public Color getBaseColour() {
        return baseColour;
    }
    
    /**
     * Returns the colour this fader is fading too.
     * 
     * @return The fade colour
     */
    public Color getFadeToColour() {
        return fadeTo;
    }
    
    /**
     * Returns the time this fader will take to completely fade from the
     * base colour to the fade colour.
     * 
     * @return The fade time in seconds.
     */
    public float getFadeTime() {
        return fadeTime * MILLISECONDS_TO_SECONDS;
    }
    
    /**
     * Returns the time this fader was last updated in milliseconds, measured
     * from the same base as the time passed into the method
     * {@link #getInterpolatedColour(long) getInterpolatedColour}.
     * 
     * @return The time of the last update.
     */
    public long getLastUpdate() {
        return lastUpdate;
    }
    

    /**
     * Returns the correctly interpolated colour between the base colour and
     * the fade to colour.
     * <p>
     * If this method is called and this fader has never been updated before
     * (ie. the first time this method is called) then the base colour is
     * returned.
     * <br />
     * Every following call the amount of time passed since the last update is
     * calculated and the interpolated colour is returned.
     * <br />
     * If the amount of time past is greater than or equal to the fade time
     * then the fade colour is returned, which will always have an alpha value
     * of zero.
     * </p><p>
     * Use the method {@link #shouldReset() shouldReset} to check for the
     * fade time being passed and hence this fader returning nothing but the
     * fade colour.
     * <br />
     * Use the method {@link #reset() reset} to reset the time this fader was
     * last updated so that it can be used again.
     * </p>
     * 
     * @param  timeOfUpdate The time of the update measured in milliseconds.
     * @return The interpolated colour.
     * @see    java.lang.System#currentTimeMillis
     */
    public Color getInterpolatedColour(long timeOfUpdate) {
        float weight = (float) (timeOfUpdate - firstUpdate) / fadeTime;
        Color returnColour = null;

        if (firstUpdate < 0 || weight <= 0f) {
            returnColour = baseColour;
            firstUpdate = timeOfUpdate;
            
        } else if (weight >= 1f) {
            returnColour = fadeTo;
            reset();
            
        } else {
            returnColour = new Color(
                    (int) (baseColour.getRed()   + weight * (fadeTo.getRed()   - baseColour.getRed())),
                    (int) (baseColour.getGreen() + weight * (fadeTo.getGreen() - baseColour.getGreen())),
                    (int) (baseColour.getBlue()  + weight * (fadeTo.getBlue()  - baseColour.getBlue())),
                    (int) (baseColour.getAlpha() + weight * -baseColour.getAlpha()));
        }
        
        lastUpdate = timeOfUpdate;
        
        return returnColour;
    }
    
    /**
     * Resets the time this fader was last updated so it can be used to
     * interpolate between the base colour and fade colour once again.
     */
    public void reset() {
        lastUpdate  = 0L;
        firstUpdate = -1;
    }
    
    /**
     * Returns if this fader has completely interpolated to the fade colour or
     * not.
     * 
     * @return <code>true</code> if this fader should be reset,
     *         <code>false</code> otherwise.
     */
    public boolean shouldReset() {
        return (lastUpdate - firstUpdate) >= fadeTime;
    }

    
    
    
    
    /**
     * Indicates whether some other object is "equal to" this one.
     * 
     * @param  obj The reference object with which to compare.
     * @return <code>true</code> if this object is the same as the obj
     *         argument, <code>false</code> otherwise.
     * @see    #hashCode()
     * @see    java.util.Hashtable
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        
        if (null == obj || obj.getClass() != getClass()) {
            return false;
        }
            
        TrailFader fader = (TrailFader) obj;

        return fader.baseColour.equals(baseColour) &&
               fader.fadeTo.equals(fadeTo) &&
               fader.fadeTime == fadeTime;
    }

    /**
     * Returns a hash code value for the object.
     * 
     * @return A hash code value for this object.
     * @see    java.lang.Object#equals(java.lang.Object)
     * @see    java.util.Hashtable
     */
    @Override
    public int hashCode() {
        return baseColour.hashCode() * 13 + 
               fadeTo.hashCode()     * 13 + 
               Float.floatToIntBits(fadeTime);
    }

    /**
     * Returns a string representation of the object.
     * 
     * @return a string representation of the object.
     */
    @Override
    public String toString() {
        StringBuilder buf = new StringBuilder(50);
        
        buf.append(getClass().getName());
        buf.append("[baseColour=").append(baseColour);
        buf.append(",fadeToColour=").append(fadeTo);
        buf.append(",fadeTime=").append(fadeTime);
        buf.append(']');
        
        return buf.toString();
    }

    /**
     * Creates and returns a copy of this object reset to its base state
     * (ie. The state this object is in after {@link #reset() reset} is called).
     * 
     * @return A clone of this instance.
     * @see    java.lang.Cloneable
     */
    @Override
    public Object clone() 
        throws CloneNotSupportedException 
    {
        TrailFader clone = (TrailFader) super.clone();
        
        clone.baseColour  = baseColour;
        clone.fadeTime    = fadeTime;
        clone.fadeTo      = fadeTo;
        clone.lastUpdate  = lastUpdate;
        clone.firstUpdate = firstUpdate;
        
        return clone;
    }
    
}
